/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "memory_node_yaml.h"
#include "memorynode.h"
#include "attributecontainer.h"
#include "attributecontainervalidator.h"
#include "validatornode.h"
#include "attributevalidator.h"
#include <iostream>
namespace YAML
{

Node encode_impl(const AttributeContainer &rhs)
{
    Node root;
    for(const auto& attribute: rhs.getAttributes())
    {
        if(attribute->isSavable())
            root[attribute->getName()] = attribute->getValue();
    }
    for(const auto& container: rhs.getContainers())
    {
        Node child_container = encode_impl(*container);
        if(!child_container.IsNull())
            root[container->getName()] = child_container;
    }
    return root;
}

bool decode_impl(const Node &node, AttributeContainer &rhs, MemoryNode* root_node)
{
    if(!node.IsMap())
        return false;
    for(const auto& kv: node)
    {
        if(kv.second.IsScalar())
        {
            std::unique_ptr<Attribute> attr(std::make_unique<Attribute>(kv.first.as<std::string>(), kv.second.as<std::string>()));
            rhs.addAttribute(std::move(attr));
        }
        else if(kv.second.IsNull())
        {
            continue; // skip empty ones
        }
        else if(kv.second.IsMap())
        {
            std::unique_ptr<AttributeContainer> attr_container(std::make_unique<AttributeContainer>(kv.first.as<std::string>()));
            AttributeContainer* attr_container_ptr = attr_container.get();
            rhs.addContainer(std::move(attr_container));
            decode_impl(kv.second, *attr_container_ptr, root_node);
        }
        else if(kv.second.IsSequence())
        {
            // should check if a child is already there.
            // add child == rhs.name
            MemoryNode* root_child_node;
            // check for rhs's parents, from top to bottom, if there's already child
            AttributeContainer* current_parent = &rhs;
            std::vector<AttributeContainer*> parent_containers;
            while(current_parent != nullptr)
            {
                parent_containers.insert(parent_containers.begin(), current_parent);
                current_parent = current_parent->getParent();
            }
            root_child_node = root_node;
            for(auto& container: parent_containers)
            {
                if(!root_child_node->getChild(container->getName()))
                {
                    std::unique_ptr<MemoryNode> root_child(std::make_unique<MemoryNode>());
                    root_child->setType(container->getName());
                    root_child_node->addChild(std::move(root_child));
                }
                root_child_node = root_child_node->getChild(container->getName());
            }

            // add child == kv.first.as<std::string>() ( sequence name )
            std::unique_ptr<MemoryNode> sequence_child(std::make_unique<MemoryNode>());
            sequence_child->setType(kv.first.as<std::string>());
            MemoryNode* sequence_child_node = sequence_child.get();
            root_child_node->addChild(std::move(sequence_child));

            for(const auto& v: kv.second)
            {
                std::unique_ptr<MemoryNode> child(std::make_unique<MemoryNode>());
                if(decode_impl(v, *child))
                    sequence_child_node->addChild(std::move(child));
            }
        }
        else
        {
            return false;
        }
    }
    return true;
}

Node encode_impl(const MemoryNode &rhs)
{
    Node root;
    if(rhs.isSavable())
    {
        if(rhs.getValidator()->isAttributeSequence())
        {
            ValidatorNode* validator = rhs.getValidator();
            for(const auto& child: rhs.getChildren())
            {
                if(child->getValidator()->isAttributeSequence())
                {
                    root[validator->getName()][child->getType()] = encode_impl(*child)[child->getType()];
                }
                else
                {
                    root[validator->getName()].push_back(encode_impl(*child));
                }
            }
        }
        else
        {
            // attributes
            Node attrs = root[rhs.getType()];
            attrs = encode_impl(*rhs.getAttributeContainer());
            for (const auto& child: rhs.getChildren())
            {
                Node child_node = encode_impl(*child);
                if(child_node.IsNull())
                    continue;
                if(child->getValidator()->isAttributeSequence())
                {
                    Node attr_node = attrs[child->getValidator()->getName()];
                    for(const auto& kv: child_node[child->getValidator()->getName()])
                    {
                        attr_node[kv.first] = kv.second;
                    }
                }
                else if(child->getValidator()->isChildrenSequence())
                {
                    Node children_sequence = attrs[child->getType()];
                    for(const auto& v: child_node[child->getType()]["children"])
                    {
                        children_sequence.push_back(v);
                    }
                }
                else
                {
                    // children
                    Node children = attrs["children"];
                    children.push_back(child_node);
                }
            }
        }
    }
    return root;
}

bool decode_impl(const Node &node, MemoryNode &rhs)
{
    if(!node.IsMap())
        return false;
    for(const auto& kv: node)
    {
        rhs.setType(kv.first.as<std::string>());
        for(const auto& kv2: kv.second)
        {
            if(kv2.second.IsScalar())
            {
                std::unique_ptr<Attribute> attr(std::make_unique<Attribute>(kv2.first.as<std::string>(), kv2.second.as<std::string>()));
                rhs.getAttributeContainer()->addAttribute(std::move(attr));
            }
            else if(kv2.second.IsNull())    // nodes without any value
            {
                std::unique_ptr<Attribute> attr(std::make_unique<Attribute>(kv2.first.as<std::string>()));
                rhs.getAttributeContainer()->addAttribute(std::move(attr));
            }
            else if (kv2.second.IsSequence())
            {
                const std::string& sequence_name = kv2.first.as<std::string>();
                if(sequence_name == "children")
                {
                    for(const auto& v: kv2.second)
                    {
                        std::unique_ptr<MemoryNode> child(std::make_unique<MemoryNode>());
                        if(decode_impl(v, *child))
                            rhs.addChild(std::move(child));
                    }
                }
                else
                {
                    std::unique_ptr<MemoryNode> sequence_node(std::make_unique<MemoryNode>(sequence_name));
                    for(const auto& v: kv2.second)
                    {
                        std::unique_ptr<MemoryNode> child(std::make_unique<MemoryNode>());
                        if(decode_impl(v, *child))
                            sequence_node->addChild(std::move(child));
                    }
                    rhs.addChild(std::move(sequence_node));
                }
            }
            else if (kv2.second.IsMap())
            {
                std::unique_ptr<AttributeContainer> attr_container(std::make_unique<AttributeContainer>(kv2.first.as<std::string>()));
                if(decode_impl(kv2.second, *attr_container, &rhs))
                    rhs.getAttributeContainer()->addContainer(std::move(attr_container));
            }
            else
            {
                return false;
            }
        }
    }
    return true;
}

bool decode_impl(const Node &node, AttributeContainerValidator &rhs)
{
    if(!node.IsMap())
        return false;
    const std::string& type = node["type"].as<std::string>();
    if(type == "map")
    {
        for(const auto& kv: node["mapping"])
        {
            const std::string& attribute_name = kv.first.as<std::string>();
            const std::string& attribute_type = kv.second["type"].as<std::string>();
            if(attribute_type == "map")
            {
                // another attribute container
               decode_impl(kv.second, *rhs.addContainer(attribute_name));
            }
            else if (attribute_type == "seq")
            {
                // add to validator children
                ValidatorNode* root_validator = rhs.getRootParentValidator();
                AttributeContainerValidator* attribute_validator = &rhs;
                std::vector<AttributeContainerValidator*> parents;
                while(!attribute_validator->isRoot())
                {
                    parents.insert(parents.begin(), attribute_validator);
                    attribute_validator = attribute_validator->getParentContainer();
                }
                // create parent containers
                for(auto& attr_val: parents)
                {
                    const std::string& validator_name = attr_val->getName();
                    ValidatorNode* node = root_validator->getChild(validator_name);
                    if(!node)
                    {
                        root_validator = root_validator->addChildValidator(validator_name);
                        root_validator->setAttributeSequence(true);
                    }
                    else
                    {
                        root_validator = node;
                    }
                }

                ValidatorNode* new_child_validator = root_validator->addChildValidator(attribute_name);
                new_child_validator->setAttributeSequence(true);
                for(const auto& child: kv.second["sequence"])
                {
                    decode_impl(child, *new_child_validator);
                }
            }
            else
            {
                std::unique_ptr<AttributeValidator> attribute(std::make_unique<AttributeValidator>(attribute_name, SchemaValidator::validatorFactory(kv.second, attribute_name)));
                attribute->setup(kv.second);
                rhs.addAttribute(std::move(attribute));
            }
        }
    }
    else
    {
        return false;
    }
    return true;
}

bool decode_impl(const Node &node, ValidatorNode &rhs)
{
    if(!node.IsMap())
        return false;
    const std::string& root_type = node["type"].as<std::string>(); // 2
    if(root_type == "map")
    {
        const Node& root_mapping = node["mapping"]; // 3
        for(const auto& kv: root_mapping)
        {
            const std::string& node_name = kv.first.as<std::string>(); // 4
            // create element for that
            ValidatorNode* validator = rhs.addChildValidator(node_name);

            // python validator for node
            if(const Node& py_validator_node = kv.second["py_valid"])
            {
                validator->addPythonValidator(std::make_unique<PythonValidator>(kv.second, node_name));
            }
            // python menu actions
            if(const Node& py_menu_actions = kv.second["py_menu_actions"])
            {
                for(const auto& menu_action : py_menu_actions)
                {
                    const Node& action = menu_action["action"];
                    validator->addPythonMenuAction(action["name"].as<std::string>(), action["module"].as<std::string>(), action["function"].as<std::string>());
                }
            }
            // is node deprecated
            if(const Node& is_deprecated_node = kv.second["deprecated"])
            {
                validator->setDeprecated(is_deprecated_node.as<bool>());
            }
            // deprecated msg
            if(const Node& deprecated_msg = kv.second["deprecated_msg"])
            {
                validator->setDeprecatedMessage(deprecated_msg.as<std::string>());
            }

            const std::string& type2 = kv.second["type"].as<std::string>(); // 5
            if(type2 == "map")
            {
                for(const auto& kv2: kv.second["mapping"]) // 6
                {
                    const std::string& attribute_name = kv2.first.as<std::string>(); // 7
                    const std::string& attribute_type = kv2.second["type"].as<std::string>(); // 8
                    if(attribute_type == "seq")
                    {
                        if(attribute_name == "children")
                        {
                            // repeat whole process
                            for(const auto& child: kv2.second["sequence"])
                            {
                                // improve cycle detection
                                ValidatorNode * parent = rhs.getParent();

                                if(parent && parent->getChild(node_name) && parent->getParent() && parent->getParent()->getName() != "")
                                {
                                    // dont skip if grandparent is the root node

                                    // replace last child with current element (rhs)
                                    for(auto& existing_child: rhs.getChildren())
                                    {
                                        if(existing_child->getName() == node_name)
                                        {
                                            existing_child = parent->getChild(node_name);
                                            break;
                                        }
                                    }
                                }
                                else
                                {
                                    decode_impl(child, *validator);
                                }
                            }
                        }
                        else
                        {
                            ValidatorNode* child_validator = validator->addChildValidator(attribute_name);
                            child_validator->setChildrenSequence(true);
                            for(const auto& child: kv2.second["sequence"])
                            {
                                decode_impl(child, *child_validator);
                            }
                        }
                    }
                    else if(attribute_type == "map")
                    {
                        // attribute container
                        AttributeContainerValidator* attr_container_validator = validator->getAttributes()->addContainer(attribute_name);
                        // is addable?
                        if(const Node& is_addable_node = kv2.second["addable"])
                        {
                            attr_container_validator->setAddable(is_addable_node.as<bool>());
                        }
                        // is removable?
                        if(const Node& is_removable_node = kv2.second["removable"])
                        {
                            attr_container_validator->setRemovable(is_removable_node.as<bool>());
                        }
                        // is deprecated?
                        if(const Node& is_deprecated_node = kv2.second["deprecated"])
                        {
                            attr_container_validator->setDeprecated(is_deprecated_node.as<bool>());
                        }
                        // deprecated msg
                        if(const Node& deprecated_msg = kv2.second["deprecated_msg"])
                        {
                            attr_container_validator->setDeprecatedMessage(deprecated_msg.as<std::string>());
                        }

                        decode_impl(kv2.second, *attr_container_validator);
                    }
                    else
                    {
                        std::unique_ptr<AttributeValidator> attribute(std::make_unique<AttributeValidator>(attribute_name, SchemaValidator::validatorFactory(kv2.second, attribute_name)));
                        attribute->setup(kv2.second);
                        validator->getAttributes()->addAttribute(std::move(attribute));
                    }
                }
            }
            else
            {
                return false;
            }
        }
    }
    else
    {
        return false;
    }
    return true;
}


} // namespace YAML

